/*
 * Resizable simple ram filesystem for Linux.
 *
 * Copyright (C) 2000 Linus Torvalds.
 *               2000 Transmeta Corp.
 *
 * Usage limits added by David Gibson, Linuxcare Australia.
 * This file is released under the GPL.
 */

/*
 * NOTE! This filesystem is probably most useful
 * not as a real filesystem, but as an example of
 * how virtual filesystems can be written.
 *
 * It doesn't get much simpler than this. Consider
 * that this file implements the full semantics of
 * a POSIX-compliant read-write filesystem.
 *
 * Note in particular how the filesystem does not
 * need to implement any data structures of its own
 * to keep track of the virtual data: using the VFS
 * caches is sufficient.
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/highmem.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/backing-dev.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/magic.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/in.h>
#include <net/sock.h>
#include <asm/processor.h>
#include <asm/uaccess.h>

#include "ksocket.h"
#include "internal.h"
#include "ftrigger.hpp"

// _KERNEL is not def

/*
 *	required function bindings structures
 */

#define TESTFS_MAGIC	0x858459f6	// random number
#define PORT		12345
#define HOST		"127.0.0.1"
#define NAME		"[testfs]"

/*
 *	Datas to keep
 */
static int		mounted = 0;
struct super_block	*sbptr = NULL;
static int		sbflags;
static char*		sbdev_name;
static void*		sbdata;
static struct socket*	ksock;
static char		buf[BUFSZ];


/*
 *	structs for FS operations
 */

const struct address_space_operations testfs_aops =
{
  .readpage	= simple_readpage,
  .write_begin	= simple_write_begin,
  .write_end	= simple_write_end,
  .set_page_dirty = NULL, //__set_page_dirty_no_writeback,
};

const struct file_operations testfs_file_operations =
{
  .read		= do_sync_read,
  .aio_read	= generic_file_aio_read,
  .write	= do_sync_write,
  .aio_write	= generic_file_aio_write,
  .mmap		= generic_file_mmap,
  .fsync	= noop_fsync,
  .splice_read	= generic_file_splice_read,
  .splice_write	= generic_file_splice_write,
  .llseek	= generic_file_llseek,
};

const struct inode_operations testfs_file_inode_operations =
{
  .setattr	= simple_setattr,
  .getattr	= simple_getattr,
};



/*
 *	inodes management
 */

#define TESTFS_DEFAULT_MODE	0755

static const struct super_operations testfs_ops;
static const struct inode_operations testfs_dir_inode_operations;

static struct backing_dev_info testfs_backing_dev_info = {
  .name		= "testfs",
  .ra_pages	= 0,	/* No readahead */
  .capabilities	= BDI_CAP_NO_ACCT_AND_WRITEBACK |
		  BDI_CAP_MAP_DIRECT | BDI_CAP_MAP_COPY |
		  BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP,
};

int			send_to(int rv, char* buf)
{
  int			size_to_send = (((rv + 1) / 8) + 1) * 8;


  rv = ksend(ksock, buf, size_to_send, 0);
  if (rv == 0 || rv == -1)
    return rv;
  printk(KERN_INFO NAME"before recv\n");
  if (krecv(ksock, buf, BUFSZ, 0) < 0)
    return -1;
  printk(KERN_INFO NAME"ret 1\n");
  return 1;
}

struct inode*		testfs_get_inode(struct super_block *sb,
					 const struct inode *dir, int mode, dev_t dev)
{
  struct inode*		inode = new_inode(sb);

  if (inode)
    {
      inode->i_ino = get_next_ino();
      inode_init_owner(inode, dir, mode);
      inode->i_mapping->a_ops = &testfs_aops;
      inode->i_mapping->backing_dev_info = &testfs_backing_dev_info;
      mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
      mapping_set_unevictable(inode->i_mapping);
      inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
      switch (mode & S_IFMT) {
      default:
	init_special_inode(inode, mode, dev);
	break;
      case S_IFREG:
	inode->i_op = &testfs_file_inode_operations;
	inode->i_fop = &testfs_file_operations;
	break;
      case S_IFDIR:
	inode->i_op = &testfs_dir_inode_operations;
	inode->i_fop = &simple_dir_operations;
	/* directory inodes start off with i_nlink == 2 (for "." entry) */
	inc_nlink(inode);
	break;
      case S_IFLNK:
	inode->i_op = &page_symlink_inode_operations;
	break;
      }
    }
  return inode;
}


/*
 * File creation. Allocate an inode, and we're done..
 */
/* SMP-safe */
static int		testfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
{
  struct inode*		inode = testfs_get_inode(dir->i_sb, dir, mode, dev);
  int			error = -ENOSPC;

  printk(KERN_INFO NAME"mknod() : %s\n", dentry->d_name.name);
  if (inode)
    {
      d_instantiate(dentry, inode);
      dget(dentry);	/* Extra count - pin the dentry in core */
      error = 0;
      dir->i_mtime = dir->i_ctime = CURRENT_TIME;
    }
  return error;
}

static int		testfs_mkdir(struct inode * dir, struct dentry * dentry, int mode)
{
  int			retval = testfs_mknod(dir, dentry, mode | S_IFDIR, 0);
  int			rv;
  int			cmd = FT_REQ_REP_ADD;

  printk(KERN_INFO NAME"mkdir() : %s\n", dentry->d_name.name);
  if (!retval)
    {
      inc_nlink(dir);
      rv = snprintf(buf, BUFSZ, "CodeSize %d %s\0", mode, dentry->d_name.name);
      memcpy(buf, &cmd, 4);
      memcpy(buf + 4, &rv, 4);
      if ((rv = send_to(rv, buf)) < 1)
	return rv;
      printk(KERN_INFO NAME"sending:%s\n", &(buf[7]));
    }
  return retval;
}

static int		testfs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd)
{
  int			rv;
  int			cmd = FT_REQ_FILE_ADD;

  printk(KERN_INFO NAME"create() : %s\n", dentry->d_name.name);
  rv = snprintf(buf, BUFSZ, "CodeSize %d %s\0", mode, dentry->d_name.name);
  memcpy(buf, &cmd, 4);
  memcpy(buf + 4, &rv, 4);
  if ((rv = send_to(rv, buf)) < 1)
    return rv;
  printk(KERN_INFO NAME"sending:%s\n", &(buf[7]));
  return testfs_mknod(dir, dentry, mode | S_IFREG, 0);
}

static int		testfs_symlink(struct inode * dir, struct dentry *dentry, const char * symname)
{
  struct inode*		inode;
  int			error = -ENOSPC;

  printk(KERN_INFO NAME"symlink() : %s\n", dentry->d_name.name);
  inode = testfs_get_inode(dir->i_sb, dir, S_IFLNK|S_IRWXUGO, 0);
  if (inode)
    {
      int		l = strlen(symname) + 1;
      error = page_symlink(inode, symname, l);
      if (!error)
	{
	  d_instantiate(dentry, inode);
	  dget(dentry);
	  dir->i_mtime = dir->i_ctime = CURRENT_TIME;
	}
      else
	iput(inode);
    }
  return error;
}

static int		testfs_rmdir(struct inode* dir, struct dentry* dentry)
{
  return simple_rmdir(dir, dentry);
}

static int		testfs_unlink(struct inode* dir, struct dentry* dentry)
{
  int			rv;

  printk(KERN_INFO NAME"unlink() : %s\n", dentry->d_name.name);
  rv = snprintf(buf, BUFSZ, "\35 %s", dentry->d_name.name);
  buf[0] = FT_REQ_FILE_ADD;
  if ((rv = send_to(rv, buf)) < 1)
    return rv;
  return simple_unlink(dir, dentry);
}

/*
static int testfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
  struct inode* inode = dentry->d_inode;

  if (inode)
    generic_fillattr(inode, stat);
  return 0;
}
*/

static const struct inode_operations testfs_dir_inode_operations =
{
  .create	= testfs_create,
  .lookup	= simple_lookup,
  .link		= simple_link,
  .unlink	= simple_unlink,
  .symlink	= testfs_symlink,
  .mkdir	= testfs_mkdir,
  .rmdir	= testfs_rmdir,
  .mknod	= testfs_mknod,
  .rename	= simple_rename,
  .unlink	= testfs_unlink,
  //  .getattr	= testfs_getattr,
};

/*
static void		testfs_put_super(struct super_block* sb)
{
  kfree(sb->s_fs_info);
  kill_litter_super(sb);
  printk(KERN_INFO NAME"unmount:put_super()!\n");
  simple_put_super();
}
*/

static const struct super_operations testfs_ops =
{
  .statfs	= simple_statfs,
  .drop_inode	= generic_delete_inode,
  .show_options	= generic_show_options,
  //  .put_super	= testfs_put_super,
};





/*
 *	sockets functions
 *

unsigned int	inet_addr(char* ip)
{
  int a, b, c, d;
  char addr[4];

  sscanf(ip, "%d.%d.%d.%d", &a, &b, &c, &d);
  addr[0] = a;
  addr[1] = b;
  addr[2] = c;
  addr[3] = d;

  return *(unsigned int *)addr;
}
*/


struct testfs_mount_opts
{
  umode_t	mode;
};

enum
{
  Opt_mode,
  Opt_err
};

static const match_table_t tokens =
{
  {Opt_mode, "mode=%o"},
  {Opt_err, NULL}
};

struct testfs_fs_info
{
  struct testfs_mount_opts mount_opts;
};

static int	testfs_parse_options(char *data, struct testfs_mount_opts *opts)
{
  substring_t	args[MAX_OPT_ARGS];
  int		option;
  int		token;
  char		*p;

  opts->mode = TESTFS_DEFAULT_MODE;

  while ((p = strsep(&data, ",")) != NULL)
    {
      if (!*p)
	continue;

      token = match_token(p, tokens, args);
      switch (token)
	{
	case Opt_mode:
	  if (match_octal(&args[0], &option))
	    return -EINVAL;
	  opts->mode = option & S_IALLUGO;
	  break;
	  /*
	   * We might like to report bad mount options here;
	   * but traditionally testfs has ignored all mount options,
	   * and as it is used as a !CONFIG_SHMEM simple substitute
	   * for tmpfs, better continue to ignore other mount options.
	   */
	}
    }
  return 0;
}

int			testfs_fill_super(struct super_block *sb, void *data, int silent)
{
  struct testfs_fs_info	*fsi;
  struct inode		*inode = NULL;
  struct dentry		*root;
  int			err;

  printk("[testfs] filling super_block\n");
  sbptr = sb;

  save_mount_options(sb, data);

  fsi = kzalloc(sizeof(struct testfs_fs_info), GFP_KERNEL);
  sb->s_fs_info = fsi;
  if (!fsi)
    {
      err = -ENOMEM;
      goto fail;
    }

  err = testfs_parse_options(data, &fsi->mount_opts);
  if (err)
    goto fail;

  sb->s_maxbytes	= MAX_LFS_FILESIZE;
  sb->s_blocksize	= PAGE_CACHE_SIZE;
  sb->s_blocksize_bits	= PAGE_CACHE_SHIFT;
  sb->s_magic		= TESTFS_MAGIC;
  sb->s_op		= &testfs_ops;
  sb->s_time_gran	= 1;

  inode = testfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
  if (!inode)
    {
      err = -ENOMEM;
      goto fail;
    }

  root = d_alloc_root(inode);
  sb->s_root = root;
  if (!root)
    {
      err = -ENOMEM;
      goto fail;
    }

  return 0;
fail:
  kfree(fsi);
  sb->s_fs_info = NULL;
  iput(inode);
  return err;
}

struct dentry*		testfs_mount(struct file_system_type *fs_type,
				     int flags, const char *dev_name, void *data)
{
  struct dentry*	ret = mount_nodev(fs_type, flags, data, testfs_fill_super);

  if (ret != 0)
    {
      sbflags = flags;
      sbdev_name = kstrdup(dev_name, GFP_KERNEL);
      sbdata = data;
      printk("[testfs] mounted\n");
      mounted = 1;
    }
  return ret;
}

static struct dentry*	rootfs_mount(struct file_system_type *fs_type,
				     int flags, const char *dev_name, void *data)
{
  return mount_nodev(fs_type, flags|MS_NOUSER, data, testfs_fill_super);
}

static void		testfs_kill_sb(struct super_block *sb)
{
  kfree(sb->s_fs_info);
  kill_litter_super(sb);
  mounted = 0;
  printk(KERN_INFO NAME"kill_sb\n");
}

static struct file_system_type testfs_fs_type =
{
  .name		= "testfs",
  .mount	= testfs_mount,
  .kill_sb	= testfs_kill_sb,
};
static struct file_system_type rootfs_fs_type =
{
  .name		= "rootfs",
  .mount	= rootfs_mount,
  .kill_sb	= kill_litter_super,
};


static int		__init init_testfs_fs()
{
  int		err;
  int		addr_len;
  struct sockaddr_in sockaddr;

  printk(KERN_INFO NAME"init\n");

  if ((err = register_filesystem(&testfs_fs_type)))
    return err;

  if (sock_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, &ksock) < 0)
    return err;

  memset(&sockaddr, 0, sizeof(sockaddr));
  sockaddr.sin_family = AF_INET;
  sockaddr.sin_port = htons(PORT);
  sockaddr.sin_addr.s_addr = inet_addr(HOST);
  addr_len = sizeof(struct sockaddr_in);

  printk(KERN_INFO NAME": Trying to connect\n");
  if ((err = ksock->ops->connect(ksock, (struct sockaddr*) &sockaddr, addr_len, 0)) < 0)
    {
      ksock->ops->release(ksock);
      sock_release(ksock);
      printk(KERN_INFO NAME": Failed and leaving\n");
      return err;
    }
  printk(KERN_INFO NAME": Ready\n");
  return err;
}

/*
 * TESTS
 */

static int		fill_super(struct super_block *sb, void *data, int silent)
{
  static struct tree_descr files[] = {{""}};
  return simple_fill_super(sb, SECURITYFS_MAGIC, files);
}

static struct dentry*	get_sb(struct file_system_type *fs_type,
			       int flags, const char *dev_name, void *data)
{
  return mount_single(fs_type, flags, data, fill_super);
}

static void		__exit exit_testfs_fs()
{
  printk("[testfs] exit\n");
  if (mounted)
    {
      printk("[testfs] still mounted => unmounting\n");
      if (sbptr != NULL)
	{
	  //	  sbdentryptr = get_sb(&testfs_fs_type, sbflags, sbdev_name, sbdata);
	  testfs_kill_sb(sbptr);
	}
    }
  unregister_filesystem(&testfs_fs_type);
}

module_init(init_testfs_fs);
module_exit(exit_testfs_fs);

int		__init init_rootfs()
{
  int		err;

  if ((err = bdi_init(&testfs_backing_dev_info)))
    return err;

  if (register_filesystem(&rootfs_fs_type))
    bdi_destroy(&testfs_backing_dev_info);

  return err;
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("0.µ3^/71^/");
MODULE_DESCRIPTION("DESC");
